package cn.tpshion.task.task;

import cn.tpshion.task.common.MsgAction;
import cn.tpshion.task.data.MsgData;
import cn.tpshion.task.data.TaskData;
import cn.tpshion.task.data.TaskRespData;
import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelHandlerContext;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.ObjectUtils;

@Slf4j
public class TaskManager implements InitializingBean, ApplicationContextAware {

    private final Map<String, Set<Object>> taskMap = new ConcurrentHashMap<>();

    private ApplicationContext applicationContext;

    private final ThreadPoolTaskScheduler goTaskExecutor;

    public TaskManager(ThreadPoolTaskScheduler goTaskExecutor) {
        this.goTaskExecutor = goTaskExecutor;
    }

    @Override
    public void afterPropertiesSet() {
        Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(TaskListener.class);
        if (ObjectUtils.isEmpty(beanMap)) {
            return;
        }

        beanMap.forEach((beanName, bean) -> {
            TaskListener taskListener = bean.getClass().getAnnotation(TaskListener.class);
            Set<Object> objects = taskMap.get(taskListener.taskGroup());
            if (ObjectUtils.isEmpty(objects)) {
                Set<Object> set = new HashSet<>();
                set.add(bean);
                taskMap.put(taskListener.taskGroup(), set);
            } else {
                objects.add(bean);
            }
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void execute(TaskData data, ChannelHandlerContext ctx) {
        Set<Object> objects = taskMap.get(data.getTaskGroup());
        if (ObjectUtils.isEmpty(objects)) {
            return;
        }

        for (Object bean : objects) {
            Class<?> beanClazz = bean.getClass();
            Method[] methods = beanClazz.getDeclaredMethods();
            for (Method method : methods) {
                Task task = method.getDeclaredAnnotation(Task.class);
                if (Objects.isNull(task) || !task.taskName().equals(data.getTaskName())) {
                    continue;
                }

                goTaskExecutor.submit(() -> {
                    TaskRespData respData = new TaskRespData();
                    respData.setTaskId(data.getTaskId());
                    respData.setTaskHisId(data.getTaskHisId());
                    try {
                        if (checkTaskParam(method)) {
                            method.invoke(bean, data);
                        } else {
                            method.invoke(bean, null);
                        }
                        respData.setStatus(1);
                    } catch (Exception e) {
                        respData.setStatus(2);
                        log.error("task exec error:{}", e.getMessage(), e);
                    } finally {
                        ctx.writeAndFlush(MsgData.ofJson(MsgAction.TASK_RESP, JSON.toJSONString(respData)));
                    }
                });
            }
        }
    }

    private boolean checkTaskParam(Method method) {
        for (Parameter parameter : method.getParameters()) {
            if (parameter.isAnnotationPresent(TaskParam.class)) {
                return true;
            }
        }
        return false;
    }
}
